#include "EpilScriptFunctionObject.h"
#include "EpilChunkInterpreter.h"
#include "EpilInterpreter.h"
#include "EpilInterpreterException.h"
#include "EpilPointerHelper.h"
#include <iomanip>

using namespace std;


EpilScriptFunctionObject::EpilScriptFunctionObject( EpilInterpreter * interpreter, const list<tstring> & parameters, const EpilChunkCode * block, bool hasVariousParameters )
: EpilFunctionObject( interpreter )
, m_parameters( parameters )
, m_hasVariousParameters( hasVariousParameters )
, m_block( block )
{
}


EpilScriptFunctionObject::~EpilScriptFunctionObject()
{
}


tstring EpilScriptFunctionObject::ToString() const
{
	tstringstream ret;
	ret << t("Function: 0x") << this;
	return ret.str();
}


bool EpilScriptFunctionObject::operator==( const EpilObject & rhs ) const
{
	if( rhs.GetType() != EOT_Function )
	{
		return false;
	}
	const EpilFunctionObject & function = static_cast<const EpilFunctionObject &>( rhs );

	if( function.GetFunctionType() != this->GetFunctionType() )
	{
		return false;
	}

	const EpilScriptFunctionObject & functionObj = static_cast<const EpilScriptFunctionObject &>( rhs );
	return this->GetBlock() == functionObj.GetBlock();
}


EpilObjectVector EpilScriptFunctionObject::Invoke( const EpilObjectVector & params ) const
{
	EpilObjectVector actualParams = params;
	EpilObjectVector variousParameters;
	if( m_parameters.size() >= actualParams.size() )
	{
		size_t extraNilCount = m_parameters.size() - actualParams.size();
		for( size_t i = 0; i < extraNilCount; i++ )
		{
			actualParams.push_back( this->GetInterpreter()->CreateNilObject() );
		}
	}
	else
	{
		if( m_hasVariousParameters )
		{
			size_t variousParameterCount = actualParams.size() - m_parameters.size();
			copy( actualParams.begin() + variousParameterCount, actualParams.end(), back_inserter( variousParameters ) );
		}
	}

	this->GetInterpreter()->EnterFunctionScope( m_hasVariousParameters );
	EpilScope * curScope = this->GetInterpreter()->GetCurrentScope();

	this->GetInterpreter()->SetVariousParameters( variousParameters );

	list<tstring>::const_iterator it = m_parameters.begin();
	for( size_t i = 0; i < m_parameters.size(); i++, ++it )
	{
		tstring name = *it;
		EpilObject * value = actualParams[i];
		curScope->SetLocal( name, value );
	}

	EpilChunkInterpreter ci( this->GetInterpreter() );
	try
	{
		ci.Run( this->GetBlock() );
	}
	catch( ... )
	{
		this->GetInterpreter()->LeaveScope();
		throw;
	}

	switch( ci.GetExitReason() )
	{
	case ECER_Break:
		this->GetInterpreter()->LeaveScope();
		throw EpilInterpreterException( t("No loop to break."), ci.GetExitLocation() );
	case ECER_Continue:
		this->GetInterpreter()->LeaveScope();
		throw EpilInterpreterException( t("No loop to continue."), ci.GetExitLocation() );
	case ECER_Normal:
		this->GetInterpreter()->LeaveScope();
		return EpilObjectVector();
	case ECER_Return:
		{
			EpilObjectVector rets = this->GetInterpreter()->GetReturns();
			if( rets.empty() )
			{
				rets.push_back( this->GetInterpreter()->CreateNilObject() );
			}
			this->GetInterpreter()->LeaveScope();
			return rets;
		}
	default:
		throw EpilInterpreterException( t("Invalid exit reason detected.") );
	}
}